﻿/*
 * LINQ to LDAP
 * http://linqtoldap.codeplex.com/
 * 
 * Copyright Alan Hatter (C) 2010-2012
 * alan.hatter@gmail.com - http://hattercoding.blogspot.com/
 * 
 * This project is subject to licensing restrictions. Visit http://linqtoldap.codeplex.com/license for more information.
 */

using System;
using System.Collections.Generic;
using System.DirectoryServices.Protocols;
using System.Linq;
using LinqToLdap.Collections;
using LinqToLdap.Logging;
using LinqToLdap.Mapping;
using LinqToLdap.QueryCommands.Options;

namespace LinqToLdap.QueryCommands
{
    internal class StandardQueryCommand : QueryCommand
    {
        public StandardQueryCommand(IQueryCommandOptions options, IObjectMapping mapping)
            : base(options, mapping, true)
        {
        }

        public override object Execute(DirectoryConnection connection, SearchScope scope, int maxPageSize, bool pagingEnabled, ILinqToLdapLogger log = null, string namingContext = null)
        {
            SetDistinguishedName(namingContext);
            SearchRequest.Scope = scope;

            if (Options.SortingOptions != null)
            {
                if (GetControl<SortRequestControl>(SearchRequest.Controls) != null)
                    throw new InvalidOperationException("Only one sort request control can be sent to the server");

                SearchRequest.Controls.Add(new SortRequestControl(Options.SortingOptions.Keys) { IsCritical = false });
            }

            PageResultRequestControl pageRequest;
            if (!pagingEnabled || (pageRequest = GetControl<PageResultRequestControl>(SearchRequest.Controls)) == null && Options.PagingOptions == null)
            {
                return HandleStandardRequest(connection, log, maxPageSize, pagingEnabled);
            }

            if (Options.PagingOptions != null && pageRequest != null)
            {
                throw new InvalidOperationException("Only one page request control can be sent to the server.");
            }
            return HandlePagedRequest(connection, pageRequest, log);
        }

        public virtual object HandleStandardRequest(DirectoryConnection connection, ILinqToLdapLogger log, int maxSize, bool pagingEnabled)
        {
            if (Options.YieldNoResults)
                return Activator.CreateInstance(typeof (List<>).MakeGenericType(Options.GetEnumeratorReturnType()));

            int pageSize = 0;
            int index = 0;
            if (pagingEnabled)
            {
                int maxPageSize = Options.PageSize ?? maxSize;
                pageSize = Options.TakeSize.HasValue ? Math.Min(Options.TakeSize.Value, maxPageSize) : maxPageSize;

                index = SearchRequest.Controls.Add(new PageResultRequestControl(pageSize));
            }

            if (log != null && log.TraceEnabled) log.Trace(SearchRequest.ToLogString());
            var response = connection.SendRequest(SearchRequest) as SearchResponse;
            response.AssertSuccess();

            var list = new List<SearchResultEntry>();
            AddToList(response.Entries, ref list);

            if (pagingEnabled)
            {
                if (response.Entries.Count > 0)
                {
                    var pageResultResponseControl = GetControl<PageResultResponseControl>(response.Controls);
                    bool hasMoreResults = pageResultResponseControl != null && pageResultResponseControl.Cookie.Length > 0 && (Options.TakeSize.HasValue ? list.Count < Options.TakeSize.Value : true);
                    while (hasMoreResults)
                    {
                        SearchRequest.Controls[index] = new PageResultRequestControl(pageSize) { Cookie = pageResultResponseControl.Cookie };
                        if (log != null && log.TraceEnabled) log.Trace(SearchRequest.ToLogString());
                        response = connection.SendRequest(SearchRequest) as SearchResponse;
                        response.AssertSuccess();
                        pageResultResponseControl = GetControl<PageResultResponseControl>(response.Controls);
                        hasMoreResults = pageResultResponseControl != null && pageResultResponseControl.Cookie.Length > 0 && (Options.TakeSize.HasValue ? list.Count <= Options.TakeSize.Value : true);
                        AddToList(response.Entries, ref list);
                    }
                }
            }

            AssertSortSuccess(response.Controls);

            var enumerator = Options.GetEnumerator(Options.TakeSize.HasValue && list.Count > Options.TakeSize.Value
                                          ? list.Take(Options.TakeSize.Value).ToList()
                                          : list);

            return Activator.CreateInstance(typeof(List<>).MakeGenericType(Options.GetEnumeratorReturnType()),
                                            new[] { enumerator });
        }

        public virtual object HandlePagedRequest(DirectoryConnection connection, PageResultRequestControl pageRequest, ILinqToLdapLogger log)
        {
            if (Options.YieldNoResults)
            {
                var p = new[]
                            {
                                pageRequest.PageSize,
                                null,
                                Options.GetEnumerator(),
                                null
                            };

                return Activator.CreateInstance(typeof(LdapPage<>).MakeGenericType(Options.GetEnumeratorReturnType()), p);
            }

            if (pageRequest == null)
            {
                pageRequest = new PageResultRequestControl(Options.PagingOptions.PageSize)
                {
                    Cookie = Options.PagingOptions.NextPage
                };

                SearchRequest.Controls.Add(pageRequest);
            }
            
            if (log != null && log.TraceEnabled) log.Trace(SearchRequest.ToLogString());
            var response = connection.SendRequest(SearchRequest) as SearchResponse;

            response.AssertSuccess();

            AssertSortSuccess(response.Controls);

            var nextPage = GetControl<PageResultResponseControl>(response.Controls);
            var parameters = new[]
                                 {
                                     pageRequest.PageSize,
                                     nextPage != null ? nextPage.Cookie : null,
                                     Options.GetEnumerator(response.Entries),
                                     Options.FilterWithoutOc
                                 };
            return Activator.CreateInstance(
                typeof(LdapPage<>).MakeGenericType(Options.GetEnumeratorReturnType()),
                parameters);
        }

        public void AssertSortSuccess(DirectoryControl[] controls)
        {
            var control = GetControl<SortResponseControl>(controls);

            if (control != null && control.Result != ResultCode.Success)
            {
                throw new LdapException(
                    string.Format("Sort request returned {0}", control.Result));
            }
        }

        public void AddToList(SearchResultEntryCollection collection, ref List<SearchResultEntry> list)
        {
            for (int i = 0; i < collection.Count; i++)
            {
                list.Add(collection[i]);
            }
        }
    }
}
